﻿// Created by Windward Studios - no copyright is claimed. This code can be used in
// any manner by anyone for any reason. There is no copyright of any kind on it. You may
// use it in commercial products. You may change it without sharing those changes.
// We ask that you keep the "created by Windward Studios" in a comment at the top.

// TNS finder based on code in http://www.dotnetjunkies.com/WebLog/debasish/archive/2005/11/13.aspx and
// http://mydotnet.wordpress.com/2008/12/25/getting-oracle-tns-names-for-your-connect-to-database-form/
// TNS parser based on code in http://www.experts-exchange.com/Programming/Languages/C_Sharp/Q_21608389.html
// and http://www.codeproject.com/KB/database/TNSNames_Reader.aspx

using System.Collections;
using System.IO;
using System.Text.RegularExpressions;
using Kailua.net.windward.utils;
using Microsoft.Win32;

namespace net.windward.utils.ado.Oracle
{

	/// <summary>
	/// Parses the Orcale TNS file to find all databases visible to this system.
	/// </summary>
	public class TNSParser
	{

		/// <summary>
		/// Return all TNS entries in this system's TNS file.
		/// </summary>
		/// <returns>All TNS entries in this system's TNS file.</returns>
		public static TNSEntry [] GetAllEntries()
		{
			string[] filenames = GetTnsFiles();
			ArrayList allEntries = new ArrayList();
			foreach(string fileOn in filenames)
				allEntries.AddRange(ParseFile(fileOn));

			return (TNSEntry[])allEntries.ToArray(typeof(TNSEntry));
		}

		/// <summary>
		/// Return all TNS file(s) on this system.
		/// </summary>
		/// <returns>All TNS file(s) on this system.</returns>
		public static string[] GetTnsFiles()
		{
			ArrayList tnsNames = new ArrayList();

			using (RegistryKey masterKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\ORACLE"))
			{
				// Oracle not installed
				if (masterKey == null)
					return new string[0];

				// This should get us the main one in use. If it has any, we return just this.
				using (RegistryKey rgkAllHome = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\ORACLE\ALL_HOMES"))
				{
					if (rgkAllHome != null)
					{
						Trap.trap();
						object objLastHome = rgkAllHome.GetValue("LAST_HOME");
						if (objLastHome != null)
							using (RegistryKey rgkActualHome = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\ORACLE\HOME" + objLastHome))
							{
								if (rgkActualHome != null)
								{
									Trap.trap();
									object objOraHome = rgkActualHome.GetValue("ORACLE_HOME");
									if (objOraHome != null)
									{
										string tnsFileName1 = TryThisFile(objOraHome.ToString());
										if (tnsFileName1 != null)
											return new string[] {tnsFileName1};
										Trap.trap();
									}
								}
							}
					}
				}

				// ok, we have no idea which one is good, so we return all of them.
				// now we walk any 10g ones.
				string[] subKeyName = masterKey.GetSubKeyNames();
				foreach (string keyOn in subKeyName)
				{
					if (keyOn.StartsWith("KEY_"))
					{
						using (RegistryKey subKey = masterKey.OpenSubKey(keyOn))
						{
							if (subKey != null)
							{
								string path = subKey.GetValue("ORACLE_HOME") as string;
								if (path != null)
								{
									string tnsFileName2 = TryThisFile(path);
									if (tnsFileName2 != null)
										tnsNames.Add(tnsFileName2);
								}
							}
						}
					}

				}

				// and now 9i
				string path3 = masterKey.GetValue("ORACLE_HOME") as string;
				if (path3 != null)
				{
					string tnsFileName3 = TryThisFile(path3);
					if (tnsFileName3 != null)
						tnsNames.Add(tnsFileName3);
				}
			}

			return (string[]) tnsNames.ToArray(typeof(string));
		}

		private static string TryThisFile(string path)
		{
			string filename = Path.GetFullPath(Path.Combine(path, "NETWORK\\ADMIN\\TNSNAMES.ORA"));
			if (File.Exists(filename))
				return filename;
			filename = Path.GetFullPath(Path.Combine(path, "NET80\\ADMIN\\TNSNAMES.ORA"));
			if (File.Exists(filename))
				return filename;
			return null;
		}

		private static TNSEntry[] ParseFile(string fileName)
		{

			string fileText;
			using (StreamReader reader = new StreamReader(fileName))
			{
				fileText = reader.ReadToEnd();
			}

			ArrayList entries = ParseText(fileText);
			return (TNSEntry[])entries.ToArray(typeof(TNSEntry));
		}

		private const string RegExPattern = @"[\n][\s]*[^\(][a-zA-Z0-9_.]+[\s]*=[\s]*\(";
		private static ArrayList ParseText(string text)
		{

			ArrayList list = new ArrayList();

			foreach (Match m in Regex.Matches(text, RegExPattern))
			{
				string name = m.Value.Trim();
				int pos = name.IndexOf(' ');
				if (pos != -1)
					name = name.Substring(0, pos).Trim();
				pos = name.IndexOf('=');
				if (pos != -1)
					name = name.Substring(0, pos).Trim();

				TNSEntry entry = new TNSEntry();
				entry.Name = name;
				list.Add(entry);
			}

			return list;

			/* doesn't work - del 4/09
			ArrayList list = new ArrayList();

			const string pattern = @"(?<name>[A-Z\-_]+\.WORLD)|" +
								   @"(PROTOCOL = (?<protocol>[A-Z]+))|" +
								   @"(HOST = (?<host>[A-Z0-9._]+))|" +
								   @"(PORT = (?<port>[0-9]+))|" +
								   @"(SID = (?<sid>[A-Za-z0-9.\-_]+))|" +
								   @"(SERVICE_NAME = (?<service>[A-Z\-._]+))";

			TNSEntry entry = null;

			foreach (Match m in Regex.Matches(text, pattern))
			{
				string name = m.Groups["name"].Value;
				string protocol = m.Groups["protocol"].Value;
				string host = m.Groups["host"].Value;
				string port = m.Groups["port"].Value;
				string service = m.Groups["service"].Value;
				string sid = m.Groups["sid"].Value;

				if ((entry == null) || (name.Length > 0))
				{
					entry = new TNSEntry();
					entry.Name = name.Replace(" =", "");
					list.Add(entry);
				}
				else
					if (protocol.Length > 0)
						entry.Protocol = protocol;
					else
						if (host.Length > 0)
							entry.Host = host;
						else
							if (port.Length > 0)
								entry.Port = port;
							else
								if (service.Length > 0)
									entry.ServiceName = service;
								else
									if (sid.Length > 0)
										entry.SystemIdentifier = sid;
			}

			return list;
			 * */
		}

		/// <summary>
		/// A TNS entry in the TNS file. This is the connection properties to connect to a database.
		/// </summary>
		public class TNSEntry
		{
			private string name = string.Empty;
			private string protocol = string.Empty;
			private string host = string.Empty;
			private string port = string.Empty;
			private string serviceName = string.Empty;
			private string systemID = string.Empty;

			/// <summary>
			/// The database name.
			/// </summary>
			public string Name
			{
				get { return name; }
				set { name = value; }
			}

			/// <summary>
			/// The connection protocol.
			/// </summary>
			public string Protocol
			{
				get { return protocol; }
				set { protocol = value; }
			}

			/// <summary>
			/// The server host.
			/// </summary>
			public string Host
			{
				get { return host; }
				set { host = value; }
			}

			/// <summary>
			/// The connection port.
			/// </summary>
			public string Port
			{
				get { return port; }
				set { port = value; }
			}

			/// <summary>
			/// The database service name.
			/// </summary>
			public string ServiceName
			{
				get { return serviceName; }
				set { serviceName = value; }
			}

			/// <summary>
			/// The database system identifier.
			/// </summary>
			public string SystemIdentifier
			{
				get { return systemID; }
				set { systemID = value; }
			}
		}
	}
}
